/*!	 context.cpp
**	 Template File
**
**	Copyright (c) 2002-2005 Robert B. Quattlebaum Jr., Adrian Bentley
**	Copyright (c) 2008 Chris Moore
**	Copyright (c) 2012-2013 Carlos López
**
**	This package is free software; you can redistribute it and/or
**	modify it under the terms of the GNU General Public License as
**	published by the Free Software Foundation; either version 2 of
**	the License, or (at your option) any later version.
**
**	This package is distributed in the hope that it will be useful,
**	but WITHOUT ANY WARRANTY; without even the implied warranty of
**	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
**	General Public License for more details.
**
*/

#ifdef USING_PCH
#	include "pch.h"
#else
#ifdef HAVE_CONFIG_H
#	include <config.h>
#endif

#include "context.h"

#include "general.h"
#include <synfig/localization.h>
#include "layer.h"
#include "string.h"
#include "vector.h"
#include "color.h"
#include "valuenode.h"
#include "transformation.h"

#include "layers/layer_pastecanvas.h"

#include "rendering/task.h"
#include "rendering/common/task/tasksurfaceempty.h"

#endif

using namespace std;
using namespace etl;
using namespace synfig;

// #define SYNFIG_PROFILE_LAYERS
// #define SYNFIG_DEBUG_LAYERS

#ifdef SYNFIG_PROFILE_LAYERS
#include <ETL/clock>
static int depth(0);
static std::map<String, float> time_table;
static std::map<String, int> run_table;
static etl::clock profile_timer;
static String curr_layer;
static void
_print_profile_report()
{
    synfig::info(">>>> Profile Report: (Times are in msecs)");
    std::map<String, float>::iterator iter;
    float total_time(0);

    for (iter = time_table.begin(); iter != time_table.end(); ++iter) {
        String layer(iter->first);
        float time(iter->second);
        int runs(run_table[layer]);
        total_time += time;
        synfig::info(" Layer \"%s\",\tExecs: %03d, Avg Time: %05.1f, Total Time: %05.1f", layer.c_str(), runs, time / runs * 1000, time * 1000);
    }

    synfig::info("Total Time: %f seconds", total_time);
    synfig::info("<<<< End of Profile Report");
}
#endif	// SYNFIG_PROFILE_LAYERS

void
IndependentContext::set_time(Time time, bool force)const
{
    IndependentContext context(*this);

    while (*context) {
        if ((*context)->active() &&
                (force || !(*context)->get_time_mark().is_equal(time))) {
            break;
        }

        ++context;
    }

    if (!*context) {
        return;
    }

    // Set up a writer lock
    RWLock::WriterLock lock((*context)->get_rw_lock());
    (*context)->set_time(context + 1, time);
}

void
IndependentContext::set_outline_grow(Real outline_grow)const
{
    IndependentContext context(*this);

    while (*context) {
        if ((*context)->active()
                && fabs((*context)->get_outline_grow_mark() - outline_grow) > 1e-8) {
            break;
        }

        ++context;
    }

    if (!*context) {
        return;
    }

    // Set up a writer lock
    RWLock::WriterLock lock((*context)->get_rw_lock());
    (*context)->set_outline_grow(context + 1, outline_grow);
}

Color
Context::get_color(const Point &pos)const
{
    Context context(*this);

    while (!context->empty()) {
        // If this layer is active, then go
        // ahead and break out of the loop
        if (context.active() && context.in_z_range()) {
            break;
        }

        // Otherwise, we want to keep searching
        // till we find either an active layer,
        // or the end of the layer list
        ++context;
    }

    // If this layer isn't defined, return alpha
    if ((context)->empty()) {
        return Color::alpha();
    }

    RWLock::ReaderLock lock((*context)->get_rw_lock());

    return (*context)->get_color(context.get_next(), pos);
}

CairoColor
Context::get_cairocolor(const Point &pos)const
{
    Context context(*this);

    while (!context->empty()) {
        // If this layer is active, then go
        // ahead and break out of the loop
        if (context.active() && context.in_z_range()) {
            break;
        }

        // Otherwise, we want to keep searching
        // till we find either an active layer,
        // or the end of the layer list
        ++context;
    }

    // If this layer isn't defined, return alpha
    if ((context)->empty()) {
        return CairoColor::alpha();
    }

    RWLock::ReaderLock lock((*context)->get_rw_lock());

    return (*context)->get_cairocolor(context.get_next(), pos);
}

Rect
Context::get_full_bounding_rect()const
{
    Context context(*this);

    while (!context->empty()) {
        // If this layer is active and visible in z_depth range,
        // then go ahead and break out of the loop
        if (context.active() && context.in_z_range()) {
            break;
        }

        // Otherwise, we want to keep searching
        // till we find either an active layer,
        // or the end of the layer list
        ++context;
    }

    // If this layer isn't defined, return zero-sized rectangle
    if (context->empty()) {
        return Rect::zero();
    }

    return (*context)->get_full_bounding_rect(context.get_next());
}

/* Profiling will go like this:
	Profile start = +, stop = -

	+
	-

	time diff is recorded

	to get the independent times we need to break at the one inside and record etc...
	so it looks more like this:

	+
	  -
	  +
		-
		+
			...
		-
		+
	  -
	  +
	-

	at each minus we must record all the info for that which we are worried about...
	each layer can do work before or after the other work is done... so both values must be recorded...
*/

void
Context::set_render_method(RenderMethod x)
{
    Context context(*this);

    if ((context)->empty()) {
        return;
    }

    (*context)->set_render_method(context.get_next(), x);
}

etl::handle<Layer>
Context::hit_check(const Point &pos)const
{
    Context context(*this);

    while (!context->empty() && context.in_z_range()) {
        // If this layer is active, then go
        // ahead and break out of the loop
        if (context.active()) {
            break;
        }

        // Otherwise, we want to keep searching
        // till we find either an active layer,
        // or the end of the layer list
        ++context;
    }

    // If this layer isn't defined, return an empty handle
    if ((context)->empty()) {
        return 0;
    }

    return (*context)->hit_check(context.get_next(), pos);
}

bool
Context::accelerated_render(Surface *surface, int quality, const RendDesc &renddesc, ProgressCallback *cb) const
{
#ifdef SYNFIG_PROFILE_LAYERS
    String layer_name(curr_layer);

    // sum the pre-work done by layer above us... (curr_layer is layer above us...)
    if (depth > 0) {
        time_table[curr_layer] += profile_timer();
    }

#endif	// SYNFIG_PROFILE_LAYERS

    const Rect bbox(renddesc.get_rect());
    const Matrix &transfromation_matrix(renddesc.get_transformation_matrix());
    // this is going to be set to true if this layer contributes
    // nothing, but it's a straight blend with non-zero amount, and so
    // it has an effect anyway
    bool straight_and_empty = false;
    etl::handle<Layer_Composite> composite;
    Context context(*this);

    // Run all layers until context is empty
    for (; !(context)->empty(); ++context) {
        // If we are not active then move on to next layer
        if (!context.active()) {
            continue;
        }

        const Rect layer_bounds(Transformation::transform_bounds(transfromation_matrix, (*context)->get_bounding_rect()));
        // Cast current layer to composite
        composite = etl::handle<Layer_Composite>::cast_dynamic(*context);

        // If the box area is less than zero or the boxes do not
        // intersect then move on to next layer, unless the layer is
        // using a straight blend and has a non-zero amount, in which
        // case it will still affect the result
        if (layer_bounds.area() <= 0.0000000000001 || !(layer_bounds && bbox)) {
            if (composite &&
                    Surface::value_type::is_straight(composite->get_blend_method()) &&
                    composite->get_amount() != 0.0f) {
                straight_and_empty = true;
                break;
            }

            continue;
        }

        // If this layer has Straight as the blend method and amount
        // is 1.0, and the layer doesn't depend on its context, then
        // we don't want to render the context
        if (composite &&
                composite->get_blend_method() == Color::BLEND_STRAIGHT &&
                composite->get_amount() == 1.0f &&
                !composite->reads_context()) {
            Layer::Handle layer = *context;

            while (!context->empty()) {
                context++;    // skip the context
            }

            return layer->accelerated_render(context, surface, quality, renddesc, cb);
        }

        // Break out of the loop--we have found a good layer
        break;
    }

    // If this layer isn't defined, return alpha
    if (context->empty() || (straight_and_empty && composite->get_amount() == 1.0f)) {
#ifdef SYNFIG_DEBUG_LAYERS
        synfig::info("Context::accelerated_render(): Hit end of list");
#endif	// SYNFIG_DEBUG_LAYERS
        // resize the surface to the given render description
        surface->set_wh(renddesc.get_w(), renddesc.get_h());
        // and clear the surface
        surface->clear();
#ifdef SYNFIG_PROFILE_LAYERS
        profile_timer.reset();
#endif	// SYNFIG_PROFILE_LAYERS
        return true;
    }

#ifdef SYNFIG_DEBUG_LAYERS
    synfig::info("Context::accelerated_render(): Descending into %s", (*context)->get_name().c_str());
#endif	// SYNFIG_DEBUG_LAYERS

    try {
        // lock the context for reading
        RWLock::ReaderLock lock((*context)->get_rw_lock());
#ifdef SYNFIG_PROFILE_LAYERS
        // go down one layer :P
        depth++;
        curr_layer = (*context)->get_name();	// make sure the layer inside is referring to the correct layer outside
        profile_timer.reset();
#endif	// SYNFIG_PROFILE_LAYERS
        bool ret;

        // this layer doesn't draw anything onto the canvas we're
        // rendering, but it uses straight blending, so we need to render
        // the stuff under us and then blit transparent pixels over it
        // using the appropriate 'amount'
        if (straight_and_empty) {
            if ((ret = Context((context.get_next())).accelerated_render(surface, quality, renddesc, cb))) {
                Surface clearsurface;
                clearsurface.set_wh(renddesc.get_w(), renddesc.get_h());
                clearsurface.clear();
                Surface::alpha_pen apen(surface->begin());
                apen.set_alpha(composite->get_amount());
                apen.set_blend_method(composite->get_blend_method());

                clearsurface.blit_to(apen);
            }
        } else {
            ret = (*context)->accelerated_render(context.get_next(), surface, quality, renddesc, cb);
        }

#ifdef SYNFIG_PROFILE_LAYERS
        // post work for the previous layer
        time_table[curr_layer] += profile_timer();

        if (run_table.count(curr_layer)) {
            run_table[curr_layer]++;
        } else {
            run_table[curr_layer] = 1;
        }

        depth--;
        curr_layer = layer_name; // we are now onto this layer (make sure the post gets recorded correctly...

        // print out the table it we're done...
        if (depth == 0) {
            _print_profile_report(), time_table.clear(), run_table.clear();
        }

        profile_timer.reset();
#endif	// SYNFIG_PROFILE_LAYERS
        return ret;
    } catch (std::bad_alloc) {
        synfig::error("Context::accelerated_render(): Layer \"%s\" threw a bad_alloc exception!", (*context)->get_name().c_str());
#ifdef _DEBUG
        return false;
#else  // _DEBUG
        ++context;
        return context.accelerated_render(surface, quality, renddesc, cb);
#endif	// _DEBUG
    } catch (...) {
        synfig::error("Context::accelerated_render(): Layer \"%s\" threw an exception, rethrowing...", (*context)->get_name().c_str());
        throw;
    }
}

bool
Context::accelerated_cairorender(cairo_t *cr, int quality, const RendDesc &renddesc, ProgressCallback *cb) const
{
#ifdef SYNFIG_PROFILE_LAYERS
    String layer_name(curr_layer);

    // sum the pre-work done by layer above us... (curr_layer is layer above us...)
    if (depth > 0) {
        time_table[curr_layer] += profile_timer();
    }

#endif	// SYNFIG_PROFILE_LAYERS

    Context context(*this);

    // Run all layers until context is empty
    for (; !(context)->empty(); ++context) {
        // If we are not active then move on to next layer
        if (!context.active()) {
            continue;
        }

        // Found one good layer
        break;
    }

    // If this layer isn't defined, return alpha
    if (context->empty()) {
#ifdef SYNFIG_DEBUG_LAYERS
        synfig::info("Context::accelerated_cairorender(): Hit end of list");
#endif	// SYNFIG_DEBUG_LAYERS
        // clear the surface
        cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR);
        cairo_paint(cr);
#ifdef SYNFIG_PROFILE_LAYERS
        profile_timer.reset();
#endif	// SYNFIG_PROFILE_LAYERS
        return true;
    }

#ifdef SYNFIG_DEBUG_LAYERS
    synfig::info("Context::accelerated_render(): Descending into %s", (*context)->get_name().c_str());
#endif	// SYNFIG_DEBUG_LAYERS

    try {
        // lock the context for reading
        RWLock::ReaderLock lock((*context)->get_rw_lock());
#ifdef SYNFIG_PROFILE_LAYERS
        // go down one layer :P
        depth++;
        curr_layer = (*context)->get_name();	// make sure the layer inside is referring to the correct layer outside
        profile_timer.reset();
#endif	// SYNFIG_PROFILE_LAYERS
        bool ret;
        // this layer doesn't draw anything onto the canvas we're
        // rendering, but it uses straight blending, so we need to render
        // the stuff under us and then blit transparent pixels over it
        // using the appropriate 'amount'
        ret = (*context)->accelerated_cairorender(context.get_next(), cr, quality, renddesc, cb);
#ifdef SYNFIG_PROFILE_LAYERS
        // post work for the previous layer
        time_table[curr_layer] += profile_timer();

        if (run_table.count(curr_layer)) {
            run_table[curr_layer]++;
        } else {
            run_table[curr_layer] = 1;
        }

        depth--;
        curr_layer = layer_name; // we are now onto this layer (make sure the post gets recorded correctly...

        // print out the table it we're done...
        if (depth == 0) {
            _print_profile_report(), time_table.clear(), run_table.clear();
        }

        profile_timer.reset();
#endif	// SYNFIG_PROFILE_LAYERS
        return ret;
    } catch (std::bad_alloc) {
        synfig::error("Context::accelerated_cairorender(): Layer \"%s\" threw a bad_alloc exception!", (*context)->get_name().c_str());
#ifdef _DEBUG
        return false;
#else  // _DEBUG
        ++context;
        return context.accelerated_cairorender(cr, quality, renddesc, cb);
#endif	// _DEBUG
    } catch (...) {
        synfig::error("Context::accelerated_cairorender(): Layer \"%s\" threw an exception, rethrowing...", (*context)->get_name().c_str());
        throw;
    }
}

//	Make rendering task using ContextParams
rendering::Task::Handle
Context::build_rendering_task() const
{
    Context context = *this;

    while (*context
            && (!context.active()
                || (!get_params().render_excluded_contexts
                    && (*context)->get_exclude_from_rendering()))) {
        ++context;
    }

    // TODO: apply z_range and z_blur (now applies in Canvas::optimize_layers)

    return *context
           ? (*context)->build_rendering_task(context.get_next())
           : rendering::Task::Handle(new rendering::TaskSurfaceEmpty());
}